import YcConfigStringTransform from 'yc-config-string-transform';

class CanvasCommunicate {

    private transform = new YcConfigStringTransform();

    private targetOrigin: string | string[] = '*';

    private toTransform = true;

    constructor(private src?: string) { }

    /**
     * 获取iframe节点信息
     * @returns 
     */
    getIframe(src = this.src) {
        const _src = src
        return _src ? document.querySelector(`iframe[src="${_src}"]`) as HTMLIFrameElement : null
    }

    /**
     * 设置iframe地址信息
     * @param {string} src iframe 地址
     * @returns 
     */
    setSrc(src: string) {
        this.src = src;
        return this;
    }

    getSrc() {
        return this.src;
    }

    /**
     * 设置数据通讯的时候是否进行转换处理
     * @param toTransform 
     * @returns 
     */
    setDataTransform(toTransform: boolean) {
        this.toTransform = toTransform;
        return this;
    }

    /**
     * 设置iframe地址信息
     * @param {string} src iframe 地址
     * @returns 
     */
    setTargetOrigin(targetOrigin: string | string[]) {
        this.targetOrigin = targetOrigin;
        return this;
    }

    getSource() {
        return {
            fullPath: window.location.href,
            origin: window.location.origin
        }
    }

    /**
     * 发送信息
     * 配置了src就会默认发送给子级
     * @param type 指令类型
     * @param data 数据
     * @param iframeNode 发送给指定的iframe
     */
    send(type: string, data: any, iframeNode: HTMLIFrameElement | string | WindowProxy | null = null) {
        try {
            const message = this.toTransform ? this.transform.toObjectString({ type, data, source: this.getSource() }) : { type, data, source: this.getSource() };
            const iframe = iframeNode ? (typeof iframeNode === 'string' ? this.getIframe(iframeNode) : iframeNode) : this.getIframe();
            if (iframe && iframe instanceof HTMLIFrameElement) {
                // 发送消息给iframe
                iframe?.contentWindow?.postMessage(message, Array.isArray(this.targetOrigin) ? '*' : this.targetOrigin);
            } else if (iframe && iframe === iframe.window) {
                iframe.postMessage(message, Array.isArray(this.targetOrigin) ? '*' : this.targetOrigin);
            } else if (!this.src) {
                // 发送消息给父窗口
                window.parent.postMessage(message, Array.isArray(this.targetOrigin) ? '*' : this.targetOrigin);
            } else {
                // 消息没有发送的提醒信息
                console.warn(`【CanvasCommunicate】Unable to find iframe[src="${this.src}"] node information, message sending failed`)
            }
        } catch (e) {
            console.warn(`【CanvasCommunicate】`, e)
        }
        return this;
    }

    /**
     * 发送给父级信息
     * @param type 指令类型
     * @param data 数据 
     */
    sendParent(type: string, data: any) {
        try {
            const message = this.toTransform ? this.transform.toObjectString({ type, data }) : { type, data };
            // 发送消息给父窗口
            window.parent.postMessage(message, Array.isArray(this.targetOrigin) ? '*' : this.targetOrigin);
        } catch (e) {
            console.warn(`【CanvasCommunicate】`, e)
        }
    }

    /**
     * 消息接收处理
     * @param callback 
     */
    accept(callback: (message: { type: string, data: any, source: any }, event: MessageEvent<any>) => void) {
        // 监听message事件
        window.addEventListener('message', (event) => {
            const iframe = this.getIframe();
            try {
                const message = this.toTransform ? this.transform.toObject(event.data) : event.data;
                if (this.src && iframe && event.source === iframe?.contentWindow) {  // 来自iframe的消息
                    callback(message, event)
                } else if (event.origin === this.targetOrigin || (Array.isArray(this.targetOrigin) && this.targetOrigin.includes(event.origin)) || this.targetOrigin === '*') {  // 来自父级的消息
                    callback(message, event)
                } else {
                    console.warn(`【CanvasCommunicate】Obtained abnormal data information`, event)
                }
            } catch (e) {
                console.warn(`【CanvasCommunicate】`, e)
            }
        })
        return this;
    }
}

/**
 * Author: 戴向天
 * Date: 2024-10-08
 * Descript: 天幕事件中心处理器
 *
 */
class TmEventCenter {
    eventMap: Record<string, Array<(...args: any[]) => void>> = {}
    interceptorMap: Record<string, (...args: any) => { isAllow?: boolean; data?: any; destroy?: boolean; }> = {}
    constructor() {
        // 事件集合
        this.eventMap = {}
        // 拦截器结合
        this.interceptorMap = {}
        return this
    }

    /**
     * 事件注册
     * @param { String } eventName 事件名称
     * @param { Function } handler 事件处理函数
     * @returns { GuShenEventCenter }
     */
    register(eventName: string, handler: (...args: any[]) => void) {
        if (eventName in this.eventMap) {
            if (Array.isArray(this.eventMap[eventName])) {
                const hasHandler = this.eventMap[eventName].includes(handler)
                if (hasHandler) {
                    console.info(
                        `【TMEC INFO】The ${eventName} event already exists, no need to register again`
                    )
                } else {
                    this.eventMap[eventName].push(handler)
                }
            } else {
                console.error(
                    `【TMEC ERROR】The ${eventName} event is unique and cannot be registered`
                )
            }
        } else {
            this.eventMap[eventName] = [handler]
        }
        return this
    }

    /**
     * 注册唯一事件
     * @param { String} eventName 事件名称
     * @param { Function } handler 事件处理函数
     * @param { Boolean } isForce 遇到已存在的是否进行强制性替换
     * @returns { GuShenEventCenter }
     */
    only(eventName: string, handler: (...args: any[]) => void, isForce = false) {
        if (eventName in this.eventMap) {
            console.warn(
                `【TMEC WARN】The ${eventName} event information already exists, the current operation may affect other places, please consider carefully`
            )
            if (isForce) {
                this.eventMap[eventName] = [handler]
            }
        } else {
            this.eventMap[eventName] = [handler]
        }
        return this
    }

    /**
     * 事件销毁
     * @param { String} eventName 事件名称
     * @param { Function } handler 事件处理函数
     * @returns { GuShenEventCenter }
     */
    destroy(eventName: string, handler?: (...args: any[]) => void) {
        if (eventName in this.eventMap) {
            // 如果没有指定消除的函数，则就是进行消除该事件名称的所有方法
            if (!handler) {
                this.eventMap[eventName] = []
            } else if (Array.isArray(this.eventMap[eventName])) {
                this.eventMap[eventName] = this.eventMap[eventName].filter(
                    (handlerItem) => handlerItem !== handler
                )
            }
        }
        return this
    }

    clear() {
        Object.keys(this.eventMap).forEach((eventName) => {
            const value = this.eventMap[eventName]
            if (Array.isArray(value)) {
                value.forEach((fn) => this.destroy(eventName, fn))
            } else {
                this.destroy(eventName, value)
            }
        })
    }

    /**
     * 执行函数
     * @param { String} eventName 事件名称
     * @param { Function } fn 事件处理函数
     * @param { Object } options 参数数据
     * @returns { Promise<Function | null> }
     */
    run(eventName: string, fn: (...args: any[]) => void, ...args: any) {
        // 采用异步的方式进行处理，可以有效的的避免函数运行等待
        return new Promise((resolve) => {
            try {
                let result: any = {
                    isAllow: true, // 是否允许下一步
                    data: null,
                    destroy: false, // 是否销毁当前函数
                }
                // 通配符事件拦截
                result =
                    (this.interceptorMap['*'] && this.interceptorMap['*'](...args)) ||
                    result
                if (!result.isAllow) {
                    return resolve(result.destroy ? fn : null)
                }
                // 指定事件拦截
                result =
                    (this.interceptorMap[eventName] &&
                        this.interceptorMap[eventName](...args)) ||
                    result
                if (!result.isAllow) {
                    return resolve(result.destroy ? fn : null)
                }
                return resolve(result.data ? fn(result.data) : fn(...args))
            } catch (e) {
                console.warn(`【TMEC ERROR】${e}`)
            }
            resolve(null)
        })
    }

    /**
     * 触发事件
     * @param { String } eventName
     * @param { Object } options 参数数据
     * @returns { GuShenEventCenter }
     */
    trigger(eventName: string, ...args: any) {
        if (eventName in this.eventMap) {
            if (Array.isArray(this.eventMap[eventName]) && this.eventMap[eventName].length > 0) {
                const promiseAll = this.eventMap[eventName].map((handler) => this.run(eventName, handler, ...args))
                Promise.all(promiseAll).then((arrayResult) => {
                    const needDestoryHandlers = arrayResult.filter((b) => b)
                    if (Array.isArray(this.eventMap[eventName])) {
                        this.eventMap[eventName] = this.eventMap[eventName].filter((handler) => !needDestoryHandlers.includes(handler))
                    }
                })
            } else {
                // this.run(eventName, this.eventMap[eventName], ...args)
            }
        } else if (eventName) {
            // console.info(`not find ${eventName} event.  Do you sure registed it?`)
        }
        return this
    }

    /**
     * 注册拦截器
     * @param { String } name 拦截器名称
     * @param { ()=>{ isAllow?: boolean; data: Object;destroy?: boolean; } } interceptor 拦截器处理方法
     * @returns { GuShenEventCenter }
     */
    registerInterceptor(name: string, interceptor: (...args: any) => { isAllow?: boolean; data?: any; destroy?: boolean; }) {
        this.interceptorMap[name] = interceptor
        return this
    }

    /**
     * 设置拦截器
     * @param eventName 
     * @param handler 
     */
    setInterceptor(
        eventName: string,
        handler?: (...args: any[]) => {
            isAllow?: boolean;
            data?: any;
            destroy?: boolean;
        }) {
        if (handler) {
            this.interceptorMap[eventName] = handler
        } else {
            delete this.interceptorMap[eventName];
        }
    }
}

type IFrameNodeType = HTMLIFrameElement | WindowProxy | string | null;

class YcIframeCommunicate {

    private communicate: CanvasCommunicate;

    private eventCenter: TmEventCenter = new TmEventCenter();

    key = '###HELLO###'

    private ID_MAP: Record<string, boolean> = {}

    id = '';

    isSuccess = false

    eventMap: Array<[(...args: any[]) => void, (...args: any[]) => void]> = [];

    preSendList: Array<() => void> = []

    private iframeNode: IFrameNodeType | (() => IFrameNodeType) = null

    constructor(communicate: CanvasCommunicate) {
        this.communicate = communicate
        this.id = this.getUUID();
        this.communicate.accept(({ type, data }, event) => this.eventCenter.trigger(type, data, event));
        return this;
    }

    getSource() {
        return this.communicate.getSource()
    }

    /**
     * 获取iframe节点信息
     * @param src  iframe地址，用于获取指定的iframe
     * @returns { HTMLIFrameElement | null }
     */
    getIframe(src?: string) {
        return this.communicate.getIframe(src);
    }

    getIframeNode() {
        if (!this.iframeNode) return null;
        if (typeof this.iframeNode === 'string') return document.querySelector(this.iframeNode) as HTMLIFrameElement | null;
        if (typeof this.iframeNode === 'function') return this.iframeNode();
        return this.iframeNode as HTMLIFrameElement | WindowProxy;
    }

    /**
     * 是否对发送以及接收的消息进行转换处理
     * @param { Boolean } toTransform 
     * @returns { YcIframeCommunicate }
     */
    setDataTransform(toTransform: boolean) {
        this.communicate.setDataTransform(toTransform);
        return this;
    }

    /**
     * 设置 iframe 地址信息
     * @param { String } src 
     * @returns { YcIframeCommunicate }
     */
    setSrc(src: string) {
        this.communicate.setSrc(src);
        return this;
    }

    getSrc() {
        return this.communicate.getSrc();
    }

    /**
     * 设置 接收来源信息
     * @param { String } src 
     * @returns { YcIframeCommunicate }
     */
    setTargetOrigin(targetOrigin: string | string[]) {
        this.communicate.setTargetOrigin(targetOrigin);
        return this;
    }

    /**
     * 发送信息
     * 配置了src就会默认发送给子级
     * @param type 指令类型
     * @param data 数据
     * @param iframeNode 发送给指定的iframe
     * @returns { YcIframeCommunicate }
     */
    send(type: string, data: any = {}, iframeNode?: HTMLIFrameElement | WindowProxy | string | null) {
        this.communicate.send(type, {
            ...data,
            source: { id: this.id }
        }, iframeNode || this.getIframeNode());
        return this;
    }

    /**
     * 发送给父级信息
     * @param type 指令类型
     * @param data 数据 
     * @returns { YcIframeCommunicate }
     */
    sendParent(type: string, data: any = {}) {
        this.communicate.sendParent(type, data);
        return this;
    }

    /**
     * 消息订阅
     * @param { String} eventName; 订阅事件的名称
     * @param { Function } handler; 指定事件信息
     * @param { Boolean } destroy； 是否销毁原有的事件信息
     * @returns { YcIframeCommunicate }
     */
    subscribe(eventName: string, handler: ((data: any, event: MessageEvent<any>) => void), destroy = false) {
        if (destroy) this.eventCenter.destroy(eventName);
        const fn: typeof handler = (data, event) => {
            const iframeNode = this.getIframeNode();
            const iframeIsWin = iframeNode && !!(iframeNode as any).window;
            // 数据信息来源iframe子级
            const fromIframeChild = iframeNode && !iframeIsWin && event.source === (iframeNode as any).contentWindow;
            // 数据信息来源 window.open
            const fromWinChild = iframeIsWin && event.source === iframeNode;
            // 数据信息来源父级
            const fromParent = !iframeNode && (!!window.opener || window.self !== window.top);
            if (!fromIframeChild && !fromParent && !fromWinChild) return false;
            const _source = { ...data.source };
            delete data.source; // 删除source信息
            return handler(data, event)
        }
        this.eventMap.push([handler, fn])
        this.eventCenter.register(eventName, fn);
        return this;
    }

    /**
     * 取消指定的订阅，若是不传入 handler 则就是将当前订阅事件全部销毁
     * @param { String} eventName; 订阅事件的名称
     * @param { Function } handler; 指定事件信息
     * @returns { YcIframeCommunicate }
     */
    destroySubscribe(eventName: string, handler?: (...args: any[]) => void) {
        const result = this.eventMap.find(item => item[0] === handler) || []
        this.eventCenter.destroy(eventName, result[1]);
        return this;
    }

    destroy() {
        this.eventCenter.clear();
        this.isSuccess = false;
        this.eventMap = [];
        this.id = ''
        this.preSendList = []
        return this;
    }

    /**
     * 设置拦截器 eventName 等于 * 的时候，则表示都进行拦截处理
     * @param eventName 
     * @param handler 
     */
    setInterceptor(eventName: string, handler?: (...args: any[]) => {
        isAllow?: boolean;
        data?: any;
        destroy?: boolean;
    }) {
        this.eventCenter.setInterceptor(eventName, handler)
        return this
    }

    getUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
            /[xy]/g,
            function (c) {
                const r = (Math.random() * 16) | 0,
                    v = c === 'x' ? r : (r & 0x3) | 0x8;
                return v.toString(16);
            }
        );
    }

    preSend(type: string, data: any = {}, iframeNode?: HTMLIFrameElement | WindowProxy | string | null) {
        this.preSendList.push(() => this.send(type, data, iframeNode))
        return this;
    }

    setIframeNode(iframeNode: typeof this.iframeNode = null) {
        this.iframeNode = iframeNode
        return this;
    }
}

export default class YcCommunicate {
    private communicate = new CanvasCommunicate();
    constructor() {
        this.communicate.setTargetOrigin('*')
    }

    start(iframeNode: Parameters<YcIframeCommunicate['setIframeNode']>[0] = null) {
        const communicate = new YcIframeCommunicate(this.communicate);
        communicate.setIframeNode(iframeNode)
        const bool = !!window.opener || window.self !== window.top
        // 如果有父级信息，并且也没有子级信息，就进行发送loaded
        if (bool && !iframeNode) communicate.send('loaded');
        return communicate
    }
}